#!/usr/bin/env bash

DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
source $DIR/../../lib/shell/common.sh

function all_info_checks(){
    irq_info_check || return 1
    memory_usage_info_check || return 1
    return 0
}

function hex_AND(){
    local var1=$1
    local var2=$2
    local length=$(maxlength $var1 $var2)
    var1=$(feed_pads $var1 $length)
    var2=$(feed_pads $var2 $length)
    local result=""

    for ((i=0; i<$length; i++)); do
        char1=${var1:i:1}
        char2=${var2:i:1}
        decimal=$((0x$char1 & 0x$char2))
        result="${result}$(echo "obase=16; ibase=10; $decimal" | bc)"
    done

    [ -n "$result" ] || return 1
    echo $result
}
function hex_OR(){
    local var1=$1
    local var2=$2
    local length=$(maxlength $var1 $var2)
    var1=$(feed_pads $var1 $length)
    var2=$(feed_pads $var2 $length)
    local result=""

    for ((i=0; i<$length; i++)); do
        char1=${var1:i:1}
        char2=${var2:i:1}
        decimal=$((0x$char1 | 0x$char2))
        result="${result}$(echo "obase=16; ibase=10; $decimal" | bc)"
    done

    [ -n "$result" ] || return 1
    echo $result
}
function maxlength(){
    local var1=$1
    local var2=$2
    if [ ${#var1} -gt ${#var2} ]; then
        echo ${#var1}
    else
        echo ${#var2}
    fi
}
function feed_pads(){
    local var=$1
    local length=$2
    if [ "${#var}" -lt "${length}" ]; then
        local count=$(($length - ${#var}))
        zeros=$(printf "%0${count}d" 0)
        echo $zeros$var
    else
        echo $var
    fi
}

function check_all_zeros(){
    local var=$1
    if [ $(echo "$var" | grep -o "0" | wc -l) -lt ${#var} ]; then
        return 1
    fi
    return 0
}

function irq_info_check(){
    cpu_count=$(grep -c '^processor' /proc/cpuinfo)
    get_devices || return 1

    for dev in $NETWORK_DEVICE_PCI; do
        echo "irq_info_check $dev"

        irqs=$(cat /proc/interrupts | grep comp | grep $dev | awk -F: '{print $1}')
        channel_count=$(echo $irqs | wc -w)
        check_all_mask=0
        dup_warn_msg=""

        for irqn in $irqs;do

            cpu_affinity=$(cat /proc/irq/$irqn/smp_affinity)
            cpu_affinity=$(echo $cpu_affinity | sed 's/,//g')

            check_dup_mask=$(hex_AND "$check_all_mask" "$cpu_affinity")
            check_all_zeros $check_dup_mask || dup_warn_msg="WARNING: more than one irqs of device $dev are bound to the same CPU. "

            check_all_mask=$(hex_OR "$check_all_mask" "$cpu_affinity")

        done
        echo cpu affinity for all channels: $check_all_mask

        [ -n "$dup_warn_msg" ] && echo $dup_warn_msg

        check_all_mask=$(echo "obase=2; ibase=16; $check_all_mask" | bc) # convert to string in binary format
        cpu_bound=$(echo "$check_all_mask" | grep -o "1" | wc -l) # number of '1's in check_all_mask
        [ $channel_count -gt 1 ] && [ $cpu_count -gt 1 ] && [ $cpu_bound -eq 1 ] && { echo "all channels bound to the same cpu";return 1; }

    done
    return 0
}

function get_devices() {
    NETWORK_DEVICE_PCI=""
    [ -n "$INTERFACES" ] || { echo "no interface found";return 1; }
    for interface in $INTERFACES;do
        pci_num=$(ethtool -i $interface | grep bus-info | awk '{print $NF}')
        [ -n "$pci_num" ] || continue
        NETWORK_DEVICE_PCI="${NETWORK_DEVICE_PCI}${pci_num} "
    done

	[ -n "$NETWORK_DEVICE_PCI" ] || { echo "no device found"; return 1; } 
    return 0
}

function get_drivers() {
    NETWORK_DRIVER_INFO=""
    DRIVERS=""
    [ -n "$INTERFACES" ] || { echo "no interface found";return 1; }
    for interface in $INTERFACES;do

        driver_name=$(ethtool -i $interface | grep driver|awk '{print $NF}')
        driver_ko=$(modinfo $driver_name|grep -w "filename:"|awk '{print $NF}')
        [ -n $driver_name ] || continue
        [ -n $driver_ko ] || continue

        if ! echo "$NETWORK_DRIVER_INFO" | grep -wq "$driver_name";then
            NETWORK_DRIVER_INFO="${NETWORK_DRIVER_INFO}${driver_name}:${driver_ko} "
            DRIVERS="${DRIVERS}${driver_name}\|"
        fi
    done

    [ -n "$DRIVERS" ] && DRIVERS="${DRIVERS::-2}" || { echo "no driver to test";return 1; }
    return 0
}

function memory_usage_info_check(){
    # run memstrack
    log=memory_usage_info_check.log
    memstrack --report module_summary --notui > $log 2>/dev/null &
    pid=$!
    sleep 1 # sleep enough time
    # check if memstrack run correctly
    ps -p $pid > /dev/null
    [ $? -eq 0 ] || { echo "failed to start memstrack"; return 1; }

    # load driver
    get_drivers || return 1
    rebind_faces || return 1

    kill $pid
    sleep 3 # wait for 3s for memstrack to be killed and the output is written to $log, or cat will print nothing

    # info_print
    cat $log | grep "$DRIVERS"
    [ $? -eq 0 ] || { echo "memory allocation info not printed"; return 1; }

    rm -rf $log
    return 0
}

function rebind_faces(){
    echo rebinding...

    for interface in $INTERFACES;do
        local driver_name=$(ethtool -i $interface | grep driver|awk '{print $NF}')
        local pci_num=$(ethtool -i $interface | grep bus-info | awk '{print $NF}')
        device_rebind $pci_num $driver_name || return 1
        interface_up $interface $pci_num || return 1
    done
    return 0
}

function device_rebind(){
    local device=$1
    local driver=$2
    device_unbind $device $driver || return 1

    echo "binding devices..."
    bind_path=/sys/bus/pci/drivers/$driver/bind
    [ -e $bind_path ] || { echo "no driver path";return 1; }

    sh -c "echo $device > $bind_path"
    [ $? -eq 0 ] && sleep 3 || { echo "failed to bind ${device}";return 1; }

    return 0
}

function device_unbind(){
    local device=$1
    local driver=$2

    echo "unbinding devices: $device"
    unbind_path=/sys/bus/pci/drivers/$driver/unbind
    [ -e $unbind_path ] || { echo "no driver path";return 1; }

    sh -c "echo $device > $unbind_path"
    [ $? -eq 0 ] && sleep 3 || { echo "failed to unbind ${device}";return 1; }

    return 0
}

function interface_up(){
    local interface=$1
    local old_pci=$2

    local pci_num=$(ethtool -i $interface | grep bus-info | awk '{print $NF}')
    [ -n "$pci_num" ] || { echo "cannot resume status of interface $interface";return 1; }
    [ "$old_pci" == "$pci_num" ] || { echo "pci number of interface $interface changed";return 1; }

    # ifconfig $interface up	# no need
    sleep 3

    # check status
    if ! ifconfig $interface | grep -wq "UP"; then
        echo "cannot resume state of $interface"
        return 1
    fi

    return 0
}

# # # # # # # # main # # # # # # # #
print_test_info "Network"
all_info_checks && test_pass || test_fail
